﻿using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Super_Wall_Breaker.Sources.Factories;
using Super_Wall_Breaker.Sources.GameElements.Balls;
using Super_Wall_Breaker.Sources.GameElements.Bats;
using Super_Wall_Breaker.Sources.GameElements.Bricks;
using Super_Wall_Breaker.Sources.GameElements.Items;
using Super_Wall_Breaker.Sources.TeamElements;
using Super_Wall_Breaker.Sources.TeamElements.Players;
using Super_Wall_Breaker.Sources.TeamElements.PlayersControls;
using Super_Wall_Breaker.Sources.GameElements;
using System.Collections.Generic;
using Super_Wall_Breaker.Sources.ScreenManagement;
using Super_Wall_Breaker.Sources.GameEngine;
using Microsoft.Xna.Framework.Media;
using Super_Wall_Breaker.Sources.GameElements.Sprites;
using Microsoft.Xna.Framework.Audio;
using Super_Wall_Breaker.Sources.Configuration;

namespace Super_Wall_Breaker.Sources.Screens
{
    class GameplayScreen : GameScreen
    {
        #region Fields

        float pauseAlpha;

        private SpriteBatch _spriteBatch;
        private Texture2D _background;
        private ScrollingBackground _scrollingBackground;

        private int _timeLeft = 0;

        // Game elements
        private List<Brick> _bricks = new List<Brick>();
        private List<Team> _teams = new List<Team>();
        private List<Ball> _balls = new List<Ball>();
        private List<Item> _items = new List<Item>();
        private SpriteFont _arcadeFont;


        #endregion

        #region Initialization



        /// <summary>
        /// Initializes a new instance of the <see cref="GameplayScreen"/> class.
        /// </summary>
        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
            
            //teams creation
            this._teams = TeamsFactory.CreateConfiguredTeams(this._balls, new Vector2(GlobalConfiguration.DEFAULT_BAT_SPEED, 0));

            //bricks creation
            this._bricks = WallFactory.createBasicWall();

            //balls creation
            Ball ballTop = new Ball(Vector2.Zero, Vector2.Zero, new Rectangle(0, 0, 16, 16), TeamIndex.top);
            Ball ballBottom = new Ball(Vector2.Zero, Vector2.Zero, new Rectangle(0, 0, 16, 16), TeamIndex.bottom);
            this._balls.Add(ballTop);
            this._balls.Add(ballBottom);

            GameCreationConfiguration gcc = GameCreationConfiguration.GetInstance(); //game creation configuration singleton

            this._timeLeft = gcc.TotalTime * 60000; //in ms  

            MediaPlayer.Stop();
        }



        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (Content == null)
                Content = new ContentManager(ScreenManager.Game.Services, "Content"); //content manager

            Sound sound = Sound.GetInstance; // sound singleton

            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(ScreenManager.GraphicsDevice);

            //background texture loading and font loading
            _scrollingBackground = new ScrollingBackground();
            _background = Content.Load<Texture2D>("image_repeated_background_fire");
            _arcadeFont = Content.Load<SpriteFont>("font_arcadeFont");
            _scrollingBackground.Load(ScreenManager.GraphicsDevice, _background);

            //bats texture loading
            foreach (Team team in _teams)
            {
                foreach (Player player in team.Players)
                {
                    player.Bat.LoadContent(Content, GlobalConfiguration.DEFAULT_SPRITE_SHEET);
                }
            }

            //bricks texture loading
            foreach (Brick brick in _bricks)
            {
                brick.LoadContent(Content, GlobalConfiguration.DEFAULT_SPRITE_SHEET);
                brick.Break.LoadContent(Content, GlobalConfiguration.DEFAULT_SPRITE_SHEET);
            }

            //balls texture loading
            foreach (Ball ball in _balls)
            {
                ball.LoadContent(Content, GlobalConfiguration.DEFAULT_SPRITE_SHEET);
            }            

            // A real game would probably have more content than this sample, so
            // it would take longer to load. We simulate that by delaying for a
            // while, giving you a chance to admire the beautiful loading screen.
            Thread.Sleep(1000);

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();
        }


        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            Content.Unload();
        }


        #endregion

        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, false);
            Sound sound = Sound.GetInstance; // sound singleton 

            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);

            if (IsActive)
            {
                this._timeLeft -= gameTime.ElapsedGameTime.Milliseconds; // time left update     
          
                _scrollingBackground.Update((float)gameTime.ElapsedGameTime.TotalSeconds * 20); // background movement

                GameCreationConfiguration gcc = GameCreationConfiguration.GetInstance(); // game creation configuration singleton

                if (this._timeLeft <= 0)
                {
                    // Game Ending

                    Team winner = null; // draw case

                    if (_teams[1].Score > _teams[0].Score)
                        winner = _teams[1];
                    else if (_teams[1].Score < _teams[0].Score)
                        winner = _teams[0];

                    ScreenManager.AddScreen(new EndScreen(winner), ControllingPlayer);
                }

                this.DetectsBallsCollision(gameTime); // collision engine

                //Teams updating
                foreach (Team team in _teams)
                {
                    team.Update(gameTime);
                }

                //Bricks updating
                foreach (Brick brick in _bricks)
                {
                    brick.Update(gameTime);
                }

                //ball updating
                foreach (Ball ball in _balls)
                {
                    bool? goingOutDown = ball.Update(gameTime);

                    //If the ball went out of the window by the top or the bottom
                    //We set the score of the opponents by adding their team brick scores to their final team scores
                    //Reinitialize the ball (position and direction)
                    if (goingOutDown != null)
                    {
                        bool goingDown = (bool)goingOutDown;

                        if (goingDown) // The ball going out on the bottom side.
                        {
                            this.ApplyBrickScoreToTotalScore(TeamIndex.top);
                        }
                        else // The ball going out on the top side.
                        {
                            this.ApplyBrickScoreToTotalScore(TeamIndex.bottom);
                        }                        

                        if (!gcc.SoundMuted)
                        {                         
                            SoundEffect outSE = sound.Audio_out;
                            outSE.Play(0.8F, 0.0F, 0.0f);
                        }

                        ball.Initialize(); // ball reset
                    }
                }
                
                sound.LoadContent(Content);

                if (!gcc.MusicMuted && MediaPlayer.State == MediaState.Stopped)
                {                    
                    MediaPlayer.Volume = 0.3f;
                    MediaPlayer.Play(sound.Audio_Never_Gonna_Give_You_Up);
                }
            }
        }


        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            // Look up inputs for the active player profile.
            int playerIndex = (int)ControllingPlayer.Value;

            KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
            GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];

            // The game pauses either if the user presses the pause button, or if
            // they unplug the active gamepad. This requires us to keep track of
            // whether a gamepad was ever plugged in, because we don't want to pause
            // on PC if they are playing with a keyboard and have no gamepad at all!
            GlobalConfiguration gc = GlobalConfiguration.Configuration;

            bool gamePadIsUsed = gc.GamePadOneIsUsed || gc.GamePadTwoIsUsed || gc.GamePadThreeIsUsed || gc.GamePadFourIsUsed;

            bool gamePadDisconnected = !gamePadState.IsConnected &&
                                       input.GamePadWasConnected[playerIndex] && gamePadIsUsed;

            if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
            {
                ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
            }
        }

        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            _spriteBatch.Begin();

            if (this._scrollingBackground != null)
                this._scrollingBackground.Draw(_spriteBatch);
            else
                this._spriteBatch.Draw(_background, Vector2.Zero, Color.White); 

           // Bats drawing
            foreach (Team team in _teams)
            {
                team.Draw(_spriteBatch, this._arcadeFont, this._timeLeft);
            }

            // bricks drawing
            foreach (Brick brick in _bricks)
            {
                brick.Draw(_spriteBatch);
                brick.Break.Draw(_spriteBatch);
            }

            //ball drawing
            foreach (Ball ball in _balls)
            {
                ball.Draw(_spriteBatch, _arcadeFont);
            }

            _spriteBatch.End();

            base.Draw(gameTime);
        }


        /// <summary>
        /// Detects the balls collision.
        /// </summary>
        /// <param name="gameTime">A snapshot a the current time.</param>
        private void DetectsBallsCollision(GameTime gameTime)
        {
            if (this._balls != null)
            {
                foreach (Ball ball in this._balls)
                {
                    if (ball != null)
                    {
                        this.DetectsBatsBallCollision(ball);
                        this.DetectsBricksBallCollision(ball, gameTime);
                    }
                }
            }
        }


        /// <summary>
        /// Detectses the bats/ball collisions.
        /// </summary>
        /// <param name="ball">The ball to test.</param>
        private void DetectsBatsBallCollision(Ball ball)
        {
            if (ball != null && this._teams != null)
            {
                foreach (Team team in this._teams)
                {
                    if (team != null && team.Players != null)
                    {
                        foreach (Player player in team.Players)
                        {
                            if (player != null && player.Bat != null)
                            {
                                bool? intersectsLeftRight = this.ItCollidesRightOrLeftSides(player.Bat, ball);

                                if (intersectsLeftRight != null) 
                                {
                                    //collision detected

                                    if ((bool)intersectsLeftRight)
                                    {
                                        //Intersects the right or the left side of the bats
                                        ball.Speed = new Vector2(player.Bat.Speed.X + ball.Speed.X * (1 + GlobalConfiguration.DEFAULT_MAX_SPEED_INCREMENT / 100), ball.Speed.Y * (1 + GlobalConfiguration.DEFAULT_MAX_SPEED_INCREMENT / 100));

                                    }
                                    else
                                    {
                                        //Intersects the up or the down side of the bats.

                                        //The position of the ball
                                        float p = ball.Position.X + ball.SourceRectangle.Value.Width / 2;
                                        float norme = (float)Math.Sqrt(ball.Speed.X * ball.Speed.X + ball.Speed.Y * ball.Speed.Y);
                                        float pourcentage = (p - player.Bat.Position.X) / player.Bat.SourceRectangle.Value.Width;

                                        //Test if it's the team at the top or at the bottom
                                        if (team.IdTeam.Equals(TeamIndex.bottom))
                                        {
                                            if (pourcentage > 0.25F && pourcentage < 0.75F)
                                                ball.Speed = new Vector2(-norme * ((float)Math.Cos(pourcentage * Math.PI)), -norme * ((float)Math.Sin(pourcentage * Math.PI)));
                                            else
                                            {
                                                if (pourcentage <= 0.25F)
                                                    ball.Speed = new Vector2(-norme * ((float)Math.Cos(0.25F * Math.PI)), -norme * ((float)Math.Sin(0.25 * Math.PI)));
                                                else
                                                    ball.Speed = new Vector2(-norme * ((float)Math.Cos(0.75F * Math.PI)), -norme * ((float)Math.Sin(0.75F * Math.PI)));
                                            }
                                        }
                                        else if (team.IdTeam.Equals(TeamIndex.top))
                                        {
                                            if (pourcentage > 0.25F && pourcentage < 0.75F)
                                                ball.Speed = new Vector2(-norme * ((float)Math.Cos(pourcentage * Math.PI)), norme * ((float)Math.Sin(pourcentage * Math.PI)));
                                            else
                                            {
                                                if (pourcentage <= 0.25F)
                                                    ball.Speed = new Vector2(-norme * ((float)Math.Cos(0.25F * Math.PI)), norme * ((float)Math.Sin(0.25F * Math.PI)));
                                                else
                                                    ball.Speed = new Vector2(-norme * ((float)Math.Cos(0.75F * Math.PI)), norme * ((float)Math.Sin(0.75F * Math.PI)));
                                            }
                                        }

                                        //Ball lasttouchedTeam attributs updating
                                        ball.LastTouchedTeam = team.IdTeam;

                                        //Speed increment and verification
                                        float maxSpeed = GlobalConfiguration.DEFAULT_BALL_SPEED * (1 + GlobalConfiguration.DEFAULT_MAX_SPEED_INCREMENT / 100);
                                        float nextSpeedX = ball.Speed.X * (1 + GlobalConfiguration.DEFAULT_SPEED_INCREMENT / 100);
                                        float nextSpeedY = ball.Speed.Y * (1 + GlobalConfiguration.DEFAULT_SPEED_INCREMENT / 100);

                                        if (Math.Abs(nextSpeedX) > maxSpeed)
                                            nextSpeedX = maxSpeed * Math.Sign(nextSpeedX);

                                        if (Math.Abs(nextSpeedY) > maxSpeed)
                                            nextSpeedY = maxSpeed * Math.Sign(nextSpeedY);

                                        ball.Speed = new Vector2(nextSpeedX, nextSpeedY);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Detectses the bricks/ball collision.
        /// </summary>
        /// <param name="ball">The ball to verify.</param>
        /// <param name="gameTime">A snapshot of the current time.</param>
        private void DetectsBricksBallCollision(Ball ball, GameTime gameTime)
        {

            if (ball != null && this._bricks != null)
            {
                bool oneBreakIsDestroy = false;
                int index = 0;
                Sound sound = Sound.GetInstance;

                while (index < this._bricks.Count && !oneBreakIsDestroy) // we use a while loop instead of a foreach because in 
                //the loop we have to destroy a brick and is makes some issues with the foreach iterator.
                {
                    Brick brick = this._bricks[index];

                    if (brick != null)
                    {
                        bool? intersectsRoL = this.ItCollidesRightOrLeftSides(brick, ball);

                        if (intersectsRoL != null)
                        {
                            //collision detected

                            if (this._items != null && brick.Item != null)
                                this._items.Add(brick.Item); 

                            if ((bool)intersectsRoL)
                                ball.Speed = new Vector2(-ball.Speed.X, ball.Speed.Y); // right/left side intersection
                            else
                                ball.Speed = new Vector2(ball.Speed.X, -ball.Speed.Y); //up/down side intersection

                            brick.IsTouched();

                            GameCreationConfiguration gcc = GameCreationConfiguration.GetInstance();

                            if (brick.CurrentLife == 0)
                            {
                                //brick is destroyed
                                this.IncrementBrickStore(ball.LastTouchedTeam, brick.ValueScore);
                                this._bricks.Remove(brick);
                                oneBreakIsDestroy = true;

                                if (!gcc.SoundMuted)
                                {   
                                    SoundEffect e = sound.Explosion;
                                    e.Play(0.2f, 0.0f, 0.0f);
                                }
                            }

                            brick.Update(Content);

                            if (!gcc.SoundMuted)
                            {
                                SoundEffect s = sound.Laser;
                                s.Play(0.3f, 0.0f, 0.0f);
                            }

                            Rectangle brickHitBox = (Rectangle)brick.Hitbox;
                            Rectangle ballHitBox = (Rectangle)ball.Hitbox;

                            while (ballHitBox.Intersects(brickHitBox)) // this loop avoid multiple colision
                            {
                                ball.Position = new Vector2(ball.Position.X + ball.Speed.X * gameTime.ElapsedGameTime.Milliseconds, ball.Position.Y + ball.Speed.Y * gameTime.ElapsedGameTime.Milliseconds);
                                ballHitBox = (Rectangle)ball.Hitbox;
                            }
                        }
                    }

                    index++;
                }
            }
        }

        /// <summary>
        /// This function returns true if the ball collides the main sprite (bat or brick) at the right or the left side. False otherwise.
        /// It returns null if there is no collision.
        /// </summary>
        /// <param name="mainSprite"> The sprite which we want to know the collision zone. (Bricks or Bats)</param>
        /// <param name="Ball"> The ball that collides the main sprite.</param>
        /// <returns>Returns true if the second sprite collides the main sprite at the right or the left side. False otherwise.
        /// It returns null if there is no collision.</returns>
        private bool? ItCollidesRightOrLeftSides(Sprite mainSprite, Ball ball)
        {
            // See the project report to more information about this method

            bool? collidesRoL = null;

            if (mainSprite != null && mainSprite.Position != null && mainSprite.SourceRectangle != null && ball != null && ball.Position != null && ball.SourceRectangle != null)
            {
                Rectangle mainSpriteHitBox = (Rectangle)mainSprite.Hitbox;
                Rectangle ballHitBox = (Rectangle)ball.Hitbox;

                if (ballHitBox.Intersects(mainSpriteHitBox))
                {
                    //Colision detected

                    if (ball.Position.X > mainSprite.Position.X && ball.Position.X + ball.SourceRectangle.Value.Width < mainSprite.Position.X + mainSprite.SourceRectangle.Value.Width)
                    {
                        //collides the up/down side
                        collidesRoL = false;
                    }
                    else if (ball.Position.Y > mainSprite.Position.Y && ball.Position.Y + ball.SourceRectangle.Value.Height < mainSprite.Position.Y + mainSprite.SourceRectangle.Value.Height)
                    {
                        //collides the right/left side
                        collidesRoL = true;
                    }
                    else
                    {
                        //collides a corner

                        float collisionWidth = 0;
                        float collisionHeight = 0;

                        if (ball.Speed.X > 0)
                            collisionWidth = ball.Position.X + ball.SourceRectangle.Value.Width - mainSprite.Position.X;
                        else
                            collisionWidth = mainSprite.Position.X + mainSprite.SourceRectangle.Value.Width - ball.Position.X;

                        if (ball.Speed.Y > 0)
                            collisionHeight = ball.Position.Y + ball.SourceRectangle.Value.Height - mainSprite.Position.Y;
                        else
                            collisionHeight = mainSprite.Position.Y + mainSprite.SourceRectangle.Value.Height - ball.Position.Y;

                        if (collisionWidth >= collisionHeight)
                            collidesRoL = false;
                        else
                            collidesRoL = true;
                    }
                }
            }

            return collidesRoL;
        }

        /// <summary>
        /// Applies the brick score to total score.
        /// </summary>
        /// <param name="teamIndex">Index of the team who score.</param>
        private void ApplyBrickScoreToTotalScore(TeamIndex teamIndex)
        {
            if (this._teams != null)
            {
                bool teamFound = false;
                int index = 0;

                while (index < this._teams.Count && !teamFound)
                {
                    Team team = this._teams[index];

                    if (team != null)
                    {
                        if (team.IdTeam.Equals(teamIndex))
                        {
                            team.Score += team.BrickScore;
                            teamFound = true;
                        }
                    }

                    index++;
                }
            }
        }

        /// <summary>
        /// Increments the brick store.
        /// </summary>
        /// <param name="teamIndex">Index of the team who break the brick.</param>
        /// <param name="score">The score given by the brick breaking.</param>
        private void IncrementBrickStore(TeamIndex teamIndex, int score)
        {
            if (this._teams != null)
            {
                bool teamFound = false;
                int index = 0;

                while (index < this._teams.Count && !teamFound)
                {
                    Team team = this._teams[index];

                    if (team != null)
                    {
                        if (team.IdTeam.Equals(teamIndex))
                        {
                            team.BrickScore += score;
                            teamFound = true;
                        }
                    }

                    index++;
                }
            }
        }

    }
}
